feat: ViewTabBar — context menu, overflow, indicators, inline rename, save-as-view - #628
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…s, and save-as-view - Add ViewTabBarConfig type to @object-ui/types - Create reusable ViewTabBar component in packages/plugin-view - Integrate ViewTabBar into console ObjectView - Add 26 tests covering all Phase 1 features - All 130 plugin-view tests pass Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a reusable, schema-configurable ViewTabBar to standardize “Airtable/Salesforce-style” view management UX across the ecosystem, and wires it into the console ObjectView.
Changes:
- Introduces
ViewTabBar(tabs, overflow, indicators, context menu, inline rename, save-as-view indicator) in@object-ui/plugin-viewwith new test coverage. - Extends
ObjectViewSchemain@object-ui/typeswithviewTabBar?: ViewTabBarConfigand exports the new type. - Updates console
ObjectViewto useViewTabBarinstead of inline tab rendering, and documents the milestone inROADMAP.md.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Lockfile updated, but hotcrm workspace importers were removed. |
| packages/types/src/objectql.ts | Adds ViewTabBarConfig and ObjectViewSchema.viewTabBar. |
| packages/types/src/index.ts | Re-exports ViewTabBarConfig. |
| packages/plugin-view/src/index.tsx | Exports ViewTabBar + related types. |
| packages/plugin-view/src/ViewTabBar.tsx | New reusable ViewTabBar component implementation. |
| packages/plugin-view/src/tests/ViewTabBar.test.tsx | New unit tests for ViewTabBar behavior. |
| apps/console/src/components/ObjectView.tsx | Replaces inline tabs with ViewTabBar integration. |
| ROADMAP.md | Adds completed checklist for View Tab Management UX milestone. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
| examples/crm: | ||
| dependencies: | ||
| '@hono/node-server': | ||
| specifier: ^1.19.9 | ||
| version: 1.19.9(hono@4.11.9) | ||
| '@objectstack/core': | ||
| specifier: ^3.0.7 | ||
| version: 3.0.7(pino@8.21.0) | ||
| '@objectstack/driver-memory': | ||
| specifier: ^3.0.7 | ||
| version: 3.0.7(pino@8.21.0) | ||
| '@objectstack/objectql': | ||
| specifier: ^3.0.7 | ||
| version: 3.0.7(pino@8.21.0) | ||
| '@objectstack/plugin-auth': | ||
| specifier: ^3.0.7 | ||
| version: 3.0.7(next@16.1.6(@babel/core@7.29.0)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pino@8.21.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.0.18) | ||
| '@objectstack/plugin-hono-server': | ||
| specifier: ^3.0.7 | ||
| version: 3.0.7(pino@8.21.0) | ||
| '@objectstack/runtime': | ||
| specifier: ^3.0.7 | ||
| version: 3.0.7(pino@8.21.0) | ||
| '@objectstack/spec': | ||
| specifier: ^3.0.7 | ||
| version: 3.0.7 | ||
| hono: | ||
| specifier: ^4.11.9 | ||
| version: 4.11.9 | ||
| pino: | ||
| specifier: ^8.21.0 | ||
| version: 8.21.0 | ||
| pino-pretty: | ||
| specifier: ^13.1.3 | ||
| version: 13.1.3 | ||
| devDependencies: | ||
| '@objectstack/cli': | ||
| specifier: ^3.0.7 | ||
| version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) | ||
| tsx: | ||
| specifier: ^4.21.0 | ||
| version: 4.21.0 | ||
| typescript: | ||
| specifier: ^5.9.3 | ||
| version: 5.9.3 | ||
|
|
||
| examples/hello-world: | ||
| dependencies: | ||
| '@object-ui/components': | ||
| specifier: workspace:* | ||
| version: link:../../packages/components | ||
| '@object-ui/core': | ||
| specifier: workspace:* | ||
| version: link:../../packages/core | ||
| '@object-ui/react': | ||
| specifier: workspace:* | ||
| version: link:../../packages/react | ||
| react: | ||
| specifier: 19.2.4 | ||
| version: 19.2.4 | ||
| react-dom: | ||
| specifier: 19.2.4 | ||
| version: 19.2.4(react@19.2.4) | ||
|
|
||
| examples/hotcrm/packages/ai: | ||
| dependencies: | ||
| '@objectstack/spec': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7 | ||
| axios: | ||
| specifier: ^1.13.5 | ||
| version: 1.13.5 | ||
| zod: | ||
| specifier: ^4.3.6 | ||
| version: 4.3.6 | ||
| devDependencies: | ||
| '@objectstack/cli': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) | ||
| typescript: | ||
| specifier: ^5.9.3 | ||
| version: 5.9.3 | ||
|
|
||
| examples/hotcrm/packages/analytics: | ||
| dependencies: | ||
| '@hotcrm/ai': | ||
| specifier: workspace:* | ||
| version: link:../ai | ||
| '@objectstack/spec': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7 | ||
| zod: | ||
| specifier: ^4.3.6 | ||
| version: 4.3.6 | ||
| devDependencies: | ||
| '@objectstack/cli': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) | ||
| typescript: | ||
| specifier: ^5.9.3 | ||
| version: 5.9.3 | ||
|
|
||
| examples/hotcrm/packages/community: | ||
| dependencies: | ||
| '@objectstack/spec': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7 | ||
| zod: | ||
| specifier: ^4.3.6 | ||
| version: 4.3.6 | ||
| devDependencies: | ||
| '@objectstack/cli': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) | ||
| typescript: | ||
| specifier: ^5.9.3 | ||
| version: 5.9.3 | ||
|
|
||
| examples/hotcrm/packages/core: | ||
| dependencies: | ||
| '@objectstack/spec': | ||
| specifier: ^3.0.2 | ||
| version: 3.0.7 | ||
| zod: | ||
| specifier: ^4.3.6 | ||
| version: 4.3.6 | ||
|
|
||
| examples/hotcrm/packages/crm: | ||
| dependencies: | ||
| '@hotcrm/ai': | ||
| specifier: workspace:* | ||
| version: link:../ai | ||
| '@objectstack/spec': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7 | ||
| zod: | ||
| specifier: ^4.3.6 | ||
| version: 4.3.6 | ||
| devDependencies: | ||
| '@objectstack/cli': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) | ||
| typescript: | ||
| specifier: ^5.9.3 | ||
| version: 5.9.3 | ||
|
|
||
| examples/hotcrm/packages/education: | ||
| dependencies: | ||
| '@objectstack/spec': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7 | ||
| zod: | ||
| specifier: ^4.3.6 | ||
| version: 4.3.6 | ||
| devDependencies: | ||
| '@objectstack/cli': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) | ||
| typescript: | ||
| specifier: ^5.9.3 | ||
| version: 5.9.3 | ||
|
|
||
| examples/hotcrm/packages/finance: | ||
| dependencies: | ||
| '@objectstack/spec': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7 | ||
| devDependencies: | ||
| '@objectstack/cli': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) | ||
| typescript: | ||
| specifier: ^5.9.3 | ||
| version: 5.9.3 | ||
|
|
||
| examples/hotcrm/packages/financial-services: | ||
| dependencies: | ||
| '@objectstack/spec': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7 | ||
| zod: | ||
| specifier: ^4.3.6 | ||
| version: 4.3.6 | ||
| devDependencies: | ||
| '@objectstack/cli': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) | ||
| typescript: | ||
| specifier: ^5.9.3 | ||
| version: 5.9.3 | ||
|
|
||
| examples/hotcrm/packages/healthcare: | ||
| dependencies: | ||
| '@objectstack/spec': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7 | ||
| zod: | ||
| specifier: ^4.3.6 | ||
| version: 4.3.6 | ||
| devDependencies: | ||
| '@objectstack/cli': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) | ||
| typescript: | ||
| specifier: ^5.9.3 | ||
| version: 5.9.3 | ||
|
|
||
| examples/hotcrm/packages/hr: | ||
| dependencies: | ||
| '@objectstack/spec': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7 | ||
| devDependencies: | ||
| '@objectstack/cli': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) | ||
| typescript: | ||
| specifier: ^5.9.3 | ||
| version: 5.9.3 | ||
|
|
||
| examples/hotcrm/packages/integration: | ||
| dependencies: | ||
| '@hotcrm/ai': | ||
| specifier: workspace:* | ||
| version: link:../ai | ||
| '@objectstack/spec': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7 | ||
| zod: | ||
| specifier: ^4.3.6 | ||
| version: 4.3.6 | ||
| devDependencies: | ||
| '@objectstack/cli': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) | ||
| typescript: | ||
| specifier: ^5.9.3 | ||
| version: 5.9.3 | ||
|
|
||
| examples/hotcrm/packages/marketing: | ||
| dependencies: | ||
| '@objectstack/spec': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7 | ||
| devDependencies: | ||
| '@objectstack/cli': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) | ||
| typescript: | ||
| specifier: ^5.9.3 | ||
| version: 5.9.3 | ||
|
|
||
| examples/hotcrm/packages/products: | ||
| dependencies: | ||
| '@objectstack/spec': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7 | ||
| devDependencies: | ||
| '@objectstack/cli': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) | ||
| typescript: | ||
| specifier: ^5.9.3 | ||
| version: 5.9.3 | ||
|
|
||
| examples/hotcrm/packages/real-estate: | ||
| dependencies: | ||
| '@objectstack/spec': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7 | ||
| zod: | ||
| specifier: ^4.3.6 | ||
| version: 4.3.6 | ||
| devDependencies: | ||
| '@objectstack/cli': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) | ||
| typescript: | ||
| specifier: ^5.9.3 | ||
| version: 5.9.3 | ||
|
|
||
| examples/hotcrm/packages/server: | ||
| dependencies: | ||
| '@objectstack/cli': | ||
| specifier: ^3.0.2 | ||
| version: 3.0.7(@objectstack/core@3.0.7(pino@10.3.1))(esbuild@0.27.3)(pino@10.3.1) | ||
| '@objectstack/core': | ||
| specifier: ^3.0.2 | ||
| version: 3.0.7(pino@10.3.1) | ||
| '@objectstack/metadata': | ||
| specifier: ^3.0.2 | ||
| version: 3.0.7(pino@10.3.1) | ||
| '@objectstack/objectql': | ||
| specifier: ^3.0.2 | ||
| version: 3.0.7(pino@10.3.1) | ||
| '@objectstack/plugin-hono-server': | ||
| specifier: ^3.0.2 | ||
| version: 3.0.7(pino@10.3.1) | ||
| '@objectstack/runtime': | ||
| specifier: ^3.0.2 | ||
| version: 3.0.7(pino@10.3.1) | ||
| '@objectstack/spec': | ||
| specifier: ^3.0.2 | ||
| version: 3.0.7 | ||
| pino: | ||
| specifier: ^10.3.1 | ||
| version: 10.3.1 | ||
| devDependencies: | ||
| '@types/js-yaml': | ||
| specifier: ^4.0.9 | ||
| version: 4.0.9 | ||
| ts-node: | ||
| specifier: ^10.9.2 | ||
| version: 10.9.2(@swc/core@1.15.11)(@types/node@25.2.3)(typescript@5.9.3) | ||
| typescript: | ||
| specifier: ^5.9.3 | ||
| version: 5.9.3 | ||
|
|
||
| examples/hotcrm/packages/support: | ||
| dependencies: | ||
| '@objectstack/spec': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7 | ||
| devDependencies: | ||
| '@objectstack/cli': | ||
| specifier: ^3.0.3 | ||
| version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) | ||
| typescript: | ||
| specifier: ^5.9.3 | ||
| version: 5.9.3 | ||
|
|
||
| examples/kitchen-sink: | ||
| dependencies: | ||
| '@objectstack/spec': |
There was a problem hiding this comment.
pnpm-workspace.yaml includes examples/hotcrm/packages/*, but the lockfile no longer has any examples/hotcrm/packages/... importers. This will typically break pnpm install --frozen-lockfile in CI/Vercel when the hotcrm submodule is checked out. Regenerate pnpm-lock.yaml with git submodule update --init examples/hotcrm and a fresh pnpm install --no-frozen-lockfile so all hotcrm importers are present again.
| {onRenameView && ( | ||
| <ContextMenuItem | ||
| data-testid={`context-menu-rename-${view.id}`} | ||
| onClick={() => startRename(view.id)} | ||
| > | ||
| <Pencil className="h-4 w-4 mr-2" /> Rename | ||
| </ContextMenuItem> | ||
| )} |
There was a problem hiding this comment.
The Rename context-menu item is rendered whenever onRenameView is provided, but clicking it calls startRename(), which is a no-op when config.inlineRename is false. This leaves a visible menu item that does nothing. Either hide the menu item when inlineRename is false, or allow context-menu rename even when inline rename is disabled.
| DropdownMenuTrigger, | ||
| DropdownMenuContent, | ||
| DropdownMenuItem, | ||
| DropdownMenuSeparator, |
There was a problem hiding this comment.
DropdownMenuSeparator is imported from @object-ui/components but never used, which will trigger @typescript-eslint/no-unused-vars warnings. Remove the unused import or use it in the overflow dropdown if intended.
| DropdownMenuSeparator, |
| hasActiveFilters: Array.isArray((view as any).filter) && (view as any).filter.length > 0, | ||
| hasActiveSort: Array.isArray((view as any).sort) && (view as any).sort.length > 0, |
There was a problem hiding this comment.
The views.map(...) callback is already typed with optional filter/sort, but the code still casts to any to read them. Using view.filter / view.sort directly would keep this type-safe and avoid unnecessary any casts.
| hasActiveFilters: Array.isArray((view as any).filter) && (view as any).filter.length > 0, | |
| hasActiveSort: Array.isArray((view as any).sort) && (view as any).sort.length > 0, | |
| hasActiveFilters: Array.isArray(view.filter) && view.filter.length > 0, | |
| hasActiveSort: Array.isArray(view.sort) && view.sort.length > 0, |
The view tab bar lacked standard management UX found in Airtable/Salesforce: no inline add button, no right-click menu, no overflow handling, no filter indicators, no save-as-view flow.
New:
ViewTabBarcomponent (packages/plugin-view/src/ViewTabBar.tsx)Extracted from console's inline tab rendering into a reusable, schema-configurable component:
ContextMenu)maxVisibleTabs(default 6) collapse into a "More" dropdownTypes (
@object-ui/types)Added
viewTabBar?: ViewTabBarConfigtoObjectViewSchema.Console integration
apps/console/src/components/ObjectView.tsxnow uses<ViewTabBar>with all action callbacks wired (add navigates to view designer; rename/duplicate/delete/share log for future backend integration).Tests
26 new tests covering rendering, overflow, indicators, context menu, save-as-view, and inline rename. All 130 plugin-view tests pass.
Original prompt
This section details on the original issue you should resolve
<issue_title>优化视图切换��视图管理:对标主流低代码平台的管理体验</issue_title>
<issue_description>### 背景
目前 ObjectUI 已支持网格、看板、日历等多种视图,采用了类似 Airtable 的顶部标签切换。但相比微软 Power Apps、Salesforce Lightning、Airtable 等主流低代码平台,现有界面在"新增、切换、修改、管理"视图的便捷性和丰富性上还有提升空间。
主要问题/差距
参考竞品
推荐优化点(建议分阶段实施,优先高频痛点)
第一阶段(快速迭代,可一周内完成)
第二阶段(增强易用性)
技术方案建议
useViewSharinghook 丰富 UI 入口Roadmap 建议
相关资料
</issue_description>
Comments on the Issue (you are @copilot in this section)
@hotlong📊 Current State Analysis
ObjectUI already has a solid view management foundation:
✅ What's Already Built
packages/plugin-view/src/ViewSwitcher.tsxpackages/plugin-list/src/ViewSwitcher.tsxpackages/plugin-view/src/ObjectView.tsxapps/console/src/components/ObjectView.tsxpackages/plugin-designer/src/ViewDesigner.tsxpackages/react/src/hooks/useViewSharing.tspackages/react/src/__tests__/view-switching.test.tsxCurrent UX Flow (from Screenshot)
Your demo shows an Airtable-inspired tab bar:
All Tasks | Board | Calendar | Activewith a "进入设计模式" (Enter Design Mode) button. The toolbar hasHide fields | Filter | Group | Sort | Color | Comfortablecontrols.🔍 Gap Analysis: ObjectUI vs. Mainstream Platforms
Comparison Matrix
🎯 Recommended Improvements (Prioritized)
Phase 1: Quick Wins — View Tab Management UX (1-2 weeks)
These close the biggest UX gaps with the least effort:
1A. Inline "Add View" Button on Tab Bar
Currently the "Add View" is buried in a dropdown menu. Salesforce and Airtable show a
+button directly on the tab bar.